var React = require('react');
var ReactDOM = require('react-dom');
var LinkedStateMixin = require('react-addons-linked-state-mixin');

import TextInput from 'react-toolbox/lib/input';
import Dropdown from 'react-toolbox/lib/dropdown';
import Button from 'react-toolbox/lib/button';
import { List, ListItem, ListSubHeader, ListDivider, ListCheckbox } from 'react-toolbox';
import Tooltip from 'react-toolbox/lib/tooltip';
var TooltipInput = Tooltip(TextInput)

var request = {
	started:false,
	minInstance:'10',
	maxInstance:'50',
	loadGeneratorType:"normal",
	loadGeneratorOptions:{
		peak:'150',
		sigma:'120',
		value:'100',
		start:'0',
		end:'600',
		interval:'1',
		duration:'300'
	},
	scaleRules:[
	{
		scaleBy:"count",
		threshold:40,
		scaleOp:"greater",
		interval:10*60,
		value:3
	},
	{
		scaleBy:"count",
		threshold:20,
		scaleOp:"less",
		interval:10*60,
		value:-1
	}],
};

//====================== d3 functions =========================

function createChart(domId, title, yfn, ylabel, xfn) {

	var margin = {
			top: 20,
			right: 30,
			bottom: 30,
			left: 60
		},
		width = 500 - margin.left - margin.right,
		height = 400 - margin.top - margin.bottom;
	if (xfn == undefined) {
		xfn = function(d) {
			return d.step;
		};
	}

	var x = d3.scale.linear()
		.range([0, width]);
	var y = d3.scale.linear()
		.range([height, 0]);
	var svg = d3.select("#" + domId).append("svg")
		.attr("width", width + margin.left + margin.right)
		.attr("height", height + margin.top + margin.bottom)
		.append("g")
		.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

	svg.append("text")
		.attr("x", (width / 2))
		.attr("y", 0 - (margin.top / 2))
		.attr("text-anchor", "middle")
		.style("font-size", "14px")
		.style("text-decoration", "underline")
		.text(title);

	var xAxis = d3.svg.axis()
		.scale(x)
		.orient("bottom");
	var yAxis = d3.svg.axis()
		.scale(y)
		.orient("left");
	var xAxisDom =
		svg.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + height + ")")
		.call(xAxis);
	xAxisDom.append("text").attr("x", width).attr("y", -5).text("Sec");
	var yAxisDom =
		svg.append("g")
		.attr("class", "y axis")
		.call(yAxis);
	yAxisDom.append("text")
		.attr("y", 6)
		.attr("dy", ".71em")
		.style("text-anchor", "end")
		.text(ylabel);

	var pathDom = svg.append("path").attr("class", "line");
	svg.xscale = x;
	svg.yscale = y;
	var focus = svg.append("g")
		.attr("class", "focus")
		.style("display", "none");

	focus.append("circle")
		.attr("r", 4.5);

	focus.append("text")
		.attr("x", 9)
		.attr("dy", ".35em");

	function mousemove() {
		var x0 = svg.xscale.invert(d3.mouse(this)[0]),
			i = d3.bisector(function(d) {
				return xfn(d);
			}).left(statusList, x0, 1),
			d0 = statusList[i - 1],
			d1 = statusList[i],
			d = x0 - xfn(d0) > xfn(d1) - x0 ? d1 : d0;
		focus.attr("transform", "translate(" + svg.xscale(xfn(d)) + "," + svg.yscale(yfn(d)) + ")");
		focus.select("text").text(yfn(d));
	}
	svg.append("rect")
		.attr("class", "overlay")
		.attr("width", width)
		.attr("height", height)
		.on("mouseover", function() {
			focus.style("display", null);
		})
		.on("mouseout", function() {
			focus.style("display", "none");
		})
		.on("mousemove", mousemove);

	var chart = {
		'width': width,
		'height': height,
		'svg': svg,
		'xfn': xfn,
		'yfn': yfn,
		'pathDom': pathDom,
		'xAxisDom': xAxisDom,
		'yAxisDom': yAxisDom
	}
	return chart;
}

var instanceChart = createChart("chart0", "Instances",function(d){return d.serverStatus.instanceCount;});
var loadChart = createChart("chart1","Load percentage", function(d){return d.serverStatus.loadPercentage;}, "%");
var totalRequestsChart = createChart("chart2", "Serving Requests", function(d){return d.serverStatus.totalRequests;});
var requestChart = createChart("chart3", "Incoming QPS", function(d) {
	return d.loadStatus.requestComing / d.loadStatus.stepCount;
});

loadChart.baselineDom = loadChart.svg.append("path").attr("class", "baseline");

function updateChart(chart, data, fn)
{
	var xdomain = d3.extent(data, chart.xfn);
	var yMax = d3.max(data, chart.yfn);
	var yMin = d3.min(data, chart.yfn);
	var delta = Math.ceil(((yMax - yMin) * 0.1 + 5) / 2);
	var ydomain = [yMin - delta, yMax + delta];

	console.log("xdomain:"+xdomain + "  , ydomain:" + ydomain);

 	var x = d3.scale.linear()
		.range([0, chart.width]).domain(xdomain);
	var y = d3.scale.linear()
		.range([chart.height, 0]).domain(ydomain);
 	chart.svg.xscale = x;
    chart.svg.yscale = y;
	var xAxis = d3.svg.axis()
		.scale(x)
		.orient("bottom");
	var yAxis = d3.svg.axis()
		.scale(y)
		.orient("left");
  	chart.xAxisDom.call(xAxis);
 	chart.yAxisDom.call(yAxis);

  var lineGen = d3.svg.line()
		.x(function(d) {
			return x(chart.xfn(d));
		})
		.y(function(d) {
			return y(chart.yfn(d));
		});

  chart.pathDom.data(data)
      .attr("d", lineGen(data)) 
   if(fn!=undefined)
   {
   	  fn(chart, x, y);
   }
}

//===================== socket.io functions ======================
var statusList = [];
var appInstance;
var host = window.location.host;
 var socket = io('http://' + host);
  socket.on('connect', function(){

  });

  socket.on('disconnect', function(){

  });

  socket.on('finished', function()
  {
  	appInstance.setStarted(false);
  })

  socket.on('step', function(status)
  {
  	statusList.push(status);
	updateChart(instanceChart, statusList);
	updateChart(loadChart, statusList, function(chart, x ,  y)
		{
			 var lineGen = d3.svg.line()
		.x(function(d) {
			return x(d[0]);
		})
		.y(function(d) {
			return y(d[1]);
		});
		var data = [[0, 100], [100000, 100]];
			chart.baselineDom.data(data).attr("d", lineGen(data));
		});
	updateChart(totalRequestsChart, statusList);
	updateChart(requestChart, statusList);
  })

//===================== react functions ======================

  var inputStyle = {
     display: 'inline-block',
  }

var GenOptionPanel =  React.createClass({
	getInitialState:function()
	{
		return this.props.opts;
	},
	handleInputChange:function(name, value)
	{
		var next = {};
		next[name] = value;
		this.setState(next);
	},
    render: function() {
  	return (
        <div style={this.props.style}>
         <div style={{width:200, display:'inline-block'}}>
          <TextInput
          style={inputStyle}
          name="peak"
          label="Peak QPS (N,E)" 
          value={this.state.peak}
          onChange={this.handleInputChange.bind(this, "peak")} />
          </div>
          <div style={{width:200, display:'inline-block'}}>
          <TooltipInput
          style={inputStyle}
          name="sigma"
          label="sigma (N,E)"
          value={this.state.sigma}
          onChange={this.handleInputChange.bind(this, "sigma")} 
          raised primary tooltip='66% load will happen within sigma (for E) or 2*sigma (for N) seconds' />
          </div>
          <div style={{width:200, display:'inline-block'}}>
          <TextInput
          style={inputStyle}
          name="value"
          label="QPS (Uniform)"
          value={this.state.value}
          onChange={this.handleInputChange.bind(this, "value")} />
          </div>
          <div style={{width:200, display:'inline-block'}}>
          <TextInput
          style={inputStyle}
          name="start"
          label="Start at Xth sec"
          value={this.state.start}
          onChange={this.handleInputChange.bind(this, "start")} />
          </div>
          <div style={{width:200, display:'inline-block'}}>
          <TextInput
          style={inputStyle}
          name="end"
          label="End at Xth sec"
          value={this.state.end}
          onChange={this.handleInputChange.bind(this, "end")} />
          </div>
          <div style={{width:200, display:'inline-block'}}>
          <TextInput
          style={inputStyle}
          name="duration"
          label="Avg serve time (sec)"
          value={this.state.duration}
          onChange={this.handleInputChange.bind(this, "duration")} />
          </div>
          <div>
          <TextInput
          style={inputStyle}
          name="jitter"
          value={this.state.jitter}
          label="serve time jitter (sec)"
          onChange={this.handleInputChange.bind(this, "jitter")} />
          </div>
        </div>
      );
    }
});

var opcodes = [
{
	value: "less",
	label: "<"
}, {
	value: "lessOrEqual",
	label: "<="
}, {
	value: "greater",
	label: ">"
}, {
	value: "greaterOrEqual",
	label: ">="
}];

var RuleList = React.createClass({
	handleInputChange:function(name, value)
	{
		var next = this.state.payload;
		next[name] = value;
		this.setState({'payload':next});
	},
	addRule: function(event)
	{
		var rules = this.state.payload.rules;
		var rule = {
			scaleBy:this.state.payload.scaleBy,
			threshold:this.state.payload.threshold,
			scaleOp:this.state.payload.scaleOp,
			interval:this.state.payload.interval,
			value:this.state.payload.value
		};

		rules.push(rule);
		var payload = this.state.payload;
		this.state.ui.checks.push(false);
		payload['rules'] = rules;
		this.setState(this.state);
	},
	removeRule: function(event)
	{
		var checks = this.state.ui.checks;
		var rules = this.state.payload.rules;
		var newchecks = [];
		var newrules = [];
		for(var i in checks)
		{
			if(!checks[i])
			{
				newchecks.push(checks[i]);
				newrules.push(rules[i]);
			}
		}
		this.state.ui.checks = newchecks;
		this.state.payload.rules = newrules;
		this.setState(this.state);
	},
	getInitialState:function()
	{
		var checks = [];
		for(var i = 0;i<this.props.rules.length;++i)
		{
			checks.push(false);
		}
		return {
			payload: {
				scaleBy: "count",
				threshold: 40,
				scaleOp: "greater",
				interval: 10 * 60,
				value: 3,
				rules: this.props.rules
			},
			ui: {
				'checks': checks
			}
		};
	},
	composeRuleStr:function(rule)
	{
		var opLabel;
		opcodes.forEach(function(item)
		{
			if(item.value == rule.scaleOp)
			{
				opLabel = item.label;
			}
		})
		return "Check in every " +rule.interval+ "s when load "+opLabel+" " + rule.threshold+"% scale by " + rule.value+ (rule.scaleBy == 'count'? " instance(s)" : "% instance(s)");
		
	},
	onItemCheck: function(index, value)
	{
		this.state.ui.checks[index] = value;
		this.setState({'ui' : {'checks':this.state.ui.checks}});
	},
	renderListItem: function() {
		var self = this;
		var listItems = [];
		for(var i in this.state.payload.rules)
		{
			var rule = this.state.payload.rules[i];
			listItems.push(<ListCheckbox legend = {self.composeRuleStr(rule)} checked={this.state.ui.checks[i]} onChange ={ this.onItemCheck.bind(this,i)}/>)
		}
		return listItems;
	},
	render: function() {
  	return (
  		<div style = {this.props.style}>
  		<label>Rule Settings</label>
  		  <div style={{width:300}}>
  		  <Dropdown auto label="Scale by" source={
  		  	[
  		  	{value:"count", label:"count"},
  		  	{value:"percent", label:"percent"},
  		  	]
  		  } 
  		  name="scaleBy"
  		  value={this.state.payload.scaleBy}
  		  onChange = {this.handleInputChange.bind(this,"scaleBy")}/>
  		  </div>
  		  <div style={{width:300, display:'inline-block'}}>
  		  <Dropdown auto label="Condition" source={
  		  	opcodes
  		  }
  		  name="scaleOp"
  		  value={this.state.payload.scaleOp}
  		  onChange = {this.handleInputChange.bind(this,"scaleOp")}/>
  		  </div>
  		  <div style={{width:300, display:'inline-block'}}>
  		  <TextInput
          label="Load threshold"
          name="threshold"
          value={this.state.payload.threshold}
          onChange={this.handleInputChange.bind(this,"threshold")}/>
          </div>
          <div style={{width:300, display:'inline-block'}}>
          <TextInput
          label="Check interval"
          name="interval"
           value={this.state.payload.interval}
          onChange={this.handleInputChange.bind(this,"interval")} />
          </div>
          <div style={{width:300, display:'inline-block'}}>
          <TextInput
          label="count/percent"
          name="value"
          value={this.state.payload.value}
          onChange={this.handleInputChange.bind(this,"value")} />
          </div>
          <Button onClick={this.addRule}>Add</Button>
          <Button onClick={this.removeRule}>Remove</Button>
	      <List>
	      	{  
	      		this.renderListItem()
	      	}
	      </List>
	     </div>
  		);
  	}
});

var SpeedSelector = React.createClass({
	getInitialState:function()
	{
		return {speed:1};
	},

	onSpeedChange: function(value) {
		this.setState({
			'speed': value
		});
		socket.emit('speed', value);
	},

  render: function() {
      return (
      	<Dropdown auto label="Simulation speed" source={
      		[
      		{value: 1,label: "1X",},
      		{value: 5,label: "5X",},
      		{value: 20,label: "20X"},
      		{value: 100,label: "100X"}
      		]
      	}
      	name="speedMultiplier"
  		onChange = {this.onSpeedChange}
  		value = {this.state.speed}/>
    	);
   }
});

var App = React.createClass({
	handleInputChange:function(name, value)
	{
		var next = {};
		next[name] = value;
		this.setState(next);
	},
	getInitialState:function()
	{
		return request;
	},
	onStartClick:function(event)
	{
		this.state["loadGeneratorOptions"] = this.genOptionPanel.state;
		this.state["scaleRules"] = this.ruleList.state.payload.rules;
		this.state["speedMultiplier"] = this.speedSelector.state.speed;
		statusList = [];
		socket.emit("start", this.state);
		this.setState({'started':true});
	},
	setStarted:function(started)
	{
		this.setState({'started':started});
	},
	onStopClick:function(event)
	{
		socket.emit("stop");
		this.setState({'started':false});
	},
	refPanel:function(child)
	{
		this.genOptionPanel = child;
	},
	refRuleList:function(child)
	{
		this.ruleList = child;
	},
	refSpeedSelector:function(child)
	{
		this.speedSelector = child;
	},
	customItem: function(item) {
		const containerStyle = {
			display: 'flex',
			flexDirection: 'row'
		};

		const imageStyle = {
			display: 'flex',
			width: '32px',
			height: '32px',
			flexGrow: 0,
			marginRight: '8px',
			backgroundColor: '#fff'
		};

		const contentStyle = {
			display: 'flex',
			flexDirection: 'column',
			flexGrow: 2,
			fontSize: 16
		};

		return ( <div style = {
				containerStyle
			}>
			<img src = {
				item.img
			}
			style = {
				imageStyle
			}/> 
			<div style = {
				contentStyle
			}>
			<strong> {
				item.label
			} </strong> </div> </div>
		);
	},

    render: function() {
      return (
        <div style = {{width:1250}}>
        <div style = {{width:604,display: 'inline-block', border:'1px solid #CCCCCC', padding: 2}}>
        <label>Simulation Setting</label>
		<SpeedSelector ref={this.refSpeedSelector}/>
		</div>
        <div style = {{width:604,display: 'inline-block', border:'1px solid #CCCCCC', padding: 2}}>
        <label>Instance Range Setting</label><br/>
        <div style={{width:200, display:'inline-block'}}>
        <TextInput name ="minInstance" label="Min instance count" value={this.state.minInstance} onChange={this.handleInputChange.bind(this, "minInstance")} />
        </div>
        <div style={{width:200, display:'inline-block'}}>
        <TextInput name= "maxInstance" label="Max instance count" value={this.state.maxInstance} onChange={this.handleInputChange.bind(this, "maxInstance")} />
        </div>
        </div>
        <div style = {{width:1250}}>
        <div style={{width:604 ,verticalAlign:'top', display: 'inline-block', border:'1px solid #CCCCCC', padding: 2}}>
        <label>Load Generator Settings</label>
      	<Dropdown auto label="Distribution" source={
      		[
      		{value: "normal",label: "(N)ormal", img:"/img/normal.png"},
      		{value: "uniform",label: "(U)niform", img:"/img/uniform.png"},
      		{value: "exponential",label: "(E)xponential", img:"/img/exponential.png"},
      		{value: "keypath",label: "keypath (Not yet supported)"}
      		]
      	}
      	template={this.customItem}
      	name="loadGeneratorType"
      	value={this.state.loadGeneratorType}
  		onChange = {this.handleInputChange.bind(this, "loadGeneratorType")}/>
      	<GenOptionPanel ref={this.refPanel} opts = {this.state.loadGeneratorOptions}/>
      	</div>
      	<RuleList ref ={this.refRuleList} rules = {this.state.scaleRules} style={{width:604 ,verticalAlign:'top', display: 'inline-block', padding:2, border:'1px solid #CCCCCC'}}/>
      	</div>
      	<Button style={{width:300 ,display: 'inline-block'}} disabled={this.state.started} onClick={this.onStartClick}>Start</Button>
      	<Button style={{width:300 ,display: 'inline-block'}} disabled={!this.state.started} onClick={this.onStopClick}>Stop</Button>
        </div>
      );
    }
});

appInstance = ReactDOM.render(
  React.createElement(App, request),
  document.getElementById('react')
);